package com.superbytecode.cloud.web.exception;

import com.superbytecode.cloud.common.context.SpringContextUtil;
import com.superbytecode.cloud.common.exception.BusinessException;
import com.superbytecode.cloud.common.exception.ErrorCodeEnum;
import com.superbytecode.cloud.common.result.ResultWrapper;
import com.superbytecode.cloud.web.util.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolationException;
import java.util.Arrays;
import java.util.stream.Collectors;


/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: beacon-cloud
 * @Package: com.superbytecode.cloud.web.exception
 * @ClassName: GlobalExceptionHandler
 * @Description: [全局异常捕获以及错误日志管理]
 * <p>
 * - 异常可以分为业务异常、程序异常，程序异常又分为前端传参异常和后端程序异常
 * - 只有程序异常需要打印error日志，配合日志采集实时跟踪程序异常
 * - 同时如果前端传参异常需要给出400响应码且明确提示错误原因
 * </p>
 * @Author: [Lance Ting]
 * @Date: 2024/10/21 17:13
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.cn Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理业务异常
     * {@link BusinessException}
     *
     * @param exception 业务异常
     * @return 包装结果
     */
    @ResponseBody
    public ResultWrapper<Void> exceptionHandler(final BusinessException exception) {
        log.warn("业务异常: {}", exception.getMessage());
        return ResultWrapper.failure(exception.getErrcode(), exception.getMessage());
    }

    /**
     * 请求方式异常
     * {@link HttpRequestMethodNotSupportedException}
     * <p>如post方式请求get接口</p>
     *
     * @param exception 请求方式不支持异常
     * @return 包装结果
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public ResultWrapper<Void> exceptionHandler(final HttpRequestMethodNotSupportedException exception) {
        String message = exception.getMessage();
        log.error("❌接口请求方式错误，uri：{}，异常信息：{}", RequestWrapper.getRequestInfo(), message);
        return ResultWrapper.failure(ErrorCodeEnum.METHOD_NOT_ALLOWED, message);
    }

    /**
     * 参数类型不匹配异常
     * {@link MethodArgumentTypeMismatchException}
     * <p>如需要Integer但传入String</p>
     *
     * @param exception 参数类型不匹配
     * @return 包装结果
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public ResultWrapper<Void> handleException(MethodArgumentTypeMismatchException exception) {
        String errorMsg = "Parameter \"" + exception.getName() + "\" " + exception.getMessage();
        printBadRequestExceptionMsg(RequestWrapper.getRequestInfo(), errorMsg, null);
        return ResultWrapper.failure(ErrorCodeEnum.ILLEGAL_ARGUMENT, errorMsg);
    }

    /**
     * 参数绑定异常
     * {@link BindException}
     * <p>
     * 包含接口参数类型不匹配、@Valid入参校验：
     * 如前端传String，后端用数字类型Integer/Long等接收，或者@Valid注解校验不通过
     * </p>
     *
     * @param exception 参数绑定异常
     * @return 包装结果
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = BindException.class)
    public ResultWrapper<Void> exceptionHandler(final BindException exception) {
        String errorMsg = exception.getFieldErrors().stream()//
                .map(error -> "[" + error.getField() + "]" + error.getDefaultMessage())//
                .collect(Collectors.joining(","));
        printBadRequestExceptionMsg(RequestWrapper.getRequestInfo(), errorMsg, null);
        return ResultWrapper.failure(ErrorCodeEnum.ILLEGAL_ARGUMENT, errorMsg);
    }

    /**
     * 请求不可读异常(常见入参数据错误导致json反序列化异常)
     * {@link HttpMessageNotReadableException}
     *
     * @param exception 请求不可读异常
     * @return 包装结果
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public ResultWrapper<Void> exceptionHandler(HttpMessageNotReadableException exception) {
        String rootCauseMessage = ExceptionUtils.getRootCauseMessage(exception);
        printBadRequestExceptionMsg(RequestWrapper.getRequestInfo(), rootCauseMessage, null);
        return ResultWrapper.failure(ErrorCodeEnum.ILLEGAL_ARGUMENT, rootCauseMessage);
    }

    /**
     * validate 校验错误异常
     * {@link ConstraintViolationException}
     *
     * @param exception 参数校验错误异常
     * @return 包装结果
     * <p>
     * 接口入参@Validated的简单类型校验失败异常
     * </p>
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResultWrapper<Void> handleException(ConstraintViolationException exception) {
        String errMsg = exception.getConstraintViolations().stream()//
                .map(v -> ((PathImpl) v.getPropertyPath()).getLeafNode().getName() + v.getMessage())//
                .collect(Collectors.joining(";"));
        printBadRequestExceptionMsg(null, errMsg, null);
        return ResultWrapper.failure(ErrorCodeEnum.ILLEGAL_ARGUMENT, errMsg);
    }

    /**
     * validate 校验错误异常
     * {@link MethodArgumentNotValidException}
     * <p>
     * 接口入参使用@Validated的对象属性校验不通过
     * </p>
     *
     * @param exception 异常
     * @return 包装结果
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResultWrapper<Void> exceptionHandler2(MethodArgumentNotValidException exception) {
        String errMsg = exception.getBindingResult().getAllErrors().stream()//
                .map(error -> ((FieldError) error).getField() + error.getDefaultMessage())//
                .collect(Collectors.joining(";"));
        printBadRequestExceptionMsg(null, errMsg, null);
        return ResultWrapper.failure(ErrorCodeEnum.ILLEGAL_ARGUMENT, errMsg);
    }

    /**
     * 程序逻辑异常
     * {@link Exception}
     *
     * @param exception 异常信息
     * @return 包装结果
     */
    @ResponseBody
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultWrapper<Void> exceptionHandler(final Exception exception) {
        // 打印堆栈信息
        String exceptionMsgWithStack = obtainExceptionMsgWithStack(exception);
        printBadRequestExceptionMsg(RequestWrapper.getRequestInfo(), exceptionMsgWithStack, exception);
        // 生产上为了安全不响应给客户端，其他环境可以返回以提高排查效率
        return ResultWrapper.failure(ErrorCodeEnum.ERROR, SpringContextUtil.isProd() ? "" : exceptionMsgWithStack);
    }

    /**
     * 分析报错位置，帮助排查问题
     *
     * @param exception 异常
     * @return 错误信息
     */
    private String obtainExceptionMsgWithStack(Exception exception) {
        StringBuilder errorMsg = new StringBuilder(exception.toString());
        for (StackTraceElement element : exception.getStackTrace()) {
            // 将堆栈信息中第一个业务代码的位置显示出来
            if (element.toString().contains("com.superbytecode")) {
                errorMsg.append("，\n⚓异常定位：");
                errorMsg.append(element);
                break;
            }
        }
        return errorMsg.toString();
    }

    /**
     * 打印异常信息
     *
     * @param requestURI 请求路径
     * @param errMsg     异常详细
     */
    private void printBadRequestExceptionMsg(String requestURI, String errMsg, Exception exception) {
        if (StringUtils.isNotBlank(requestURI)) {
            log.error("❌接口入参异常，uri：{}，异常信息：{}", requestURI, errMsg, exception);
        } else {
            log.error("❌参数校验异常：{}", errMsg);
        }
    }
}